ts-mochaを使うとECMAScript moduleに依存したファイルでTypeError ERR_UNKNOWN_FILE_EXTENSION: Unknown file extension ".ts"が出てくる。
ts-mochaを使うとECMAScript moduleに依存したファイルだけでTypeError ERR_UNKNOWN_FILE_EXTENSION: Unknown file extension ".ts"が出てくる。
#トラブルシューティング
状況
neat-csvを使いながら、ts-mochaを用いてテストを走らせようとした場合、以下のようなエラーが生じる。
開発しているプロジェクトのpackage.jsonやtsconfig.jsonは初期状態のまま触っていない
重要な部分ピックアップ
tsconfig.json
module: CommonJS
moduleResolution: node (デフォルトで記述なし)
package.json
type: CommonJS (デフォルトで記述なし)
code:src/preprocess.test.ts
import neatCsv from "neat-csv"
neatCsv('Header1,Header2\ncell1, cell2\ncell3,cell4')
.then(data => console.log(data))
.catch(e => console.error(e))
code:zsh
❯ npx ts-mocha src/preprocess.test.ts
TypeError ERR_UNKNOWN_FILE_EXTENSION: Unknown file extension ".ts" for /Users/AppleBird/Downloads/timetable/src/preprocess.test.ts
この問題は以下の二つの小問題が絡んでいた。
(1) neat-csvというECMAScript Module(ESM)を使っていたのにも関わらず、それを利用するコードがCommonJS Moduleだった。
(2) ts-mochaへESMに依存するモジュールを読み込ませられる方法が現状ない。(2023-04-04 04:31地点)
内部仕様を見た限り、ts-mocha内で実行されるts-nodeにesm対応モードに切り替えさせる手段がない。
結論としての解決策
前提として、ts-nodeを使ってプロダクトを開発する際にはESMを避けた方が良い。
Native ECMAScript modules - ts-nodeにあるように、実験的機能であるため。
Node's ESM loader hooks are experimental and subject to change. ts-node's ESM support is as stable as possible, but it relies on APIs which node can and will break in new versions of node. Thus it is not recommended for production.
しかし実験的機能であることを考慮しないのであれば、--loader module/esmを用いた解決策は強力である。
ts-node側でできるだけ安定にしようと心がけているため。
問題(1)について考える。
neat-csvというパッケージをコード中で使うと、発生する。
- You have installed an ESM dependency but your own code compiles to CommonJS.
Solution: configure your project to compile and execute as native ESM.
引用: ERR_REQUIRE_ESM​ Troubleshooting -- ts-node
neat-csvそのものが原因ではない。
neat-csvのindex.jsおよびindex.d.tsを削除しても同様の症状が発生する。
(1) neat-csvのindex.jsはECMAScript Module; ESMである。
これはneat-csvのpackage.json中にあるtype:moduleの指定に起因している。
この設定を行うと、ECMAScript Module; ESMの類として扱われるようになる。
つまり、index.jsはindex.mjsとして扱われる。
(2) ESMに依存するモジュールはESMでなくてはならない。
neat-csvに依存するモジュールを初期のpackage.jsonのまま作ろうとすると、以下のようなエラーに見舞われる。
ERR_REQUIRE_ESM
code:output
require() of ES Module ./timetable/node_modules/neat-csv/index.js from ./timetable/src/neat-csv-tester.ts not supported.
Instead change the require of index.js in ./timetable/src/neat-csv-tester.ts to a dynamic import() which is available in all CommonJS modules.
neat-csvに依存するモジュールはも全てCommonJS Moduleではなく、ECMAScript Moduleでなくてはならない。
(2)について考えたかった
色々調べた。
ts-mochaのざっくりとしたしくみ
ts-nodeをimport(require)した上で、その上でmochaにコマンドライン引数をもろもろ渡す。
ESMに依存するモジュール(CJS/ESM)をts-mochaに入力すると、何故かエラーが出てくる現象について色々実験した
ts-node側で--esmを渡すとうまく実行できた。
※ ファイル名に先立ってこのオプションを渡さないと、--esmが有効にならない。
現状、ts-mochaの仕組みではimportされるts-nodeにオプション--esmを渡す方法がない。(2023-04-04 04:28地点)
あるいは、ts-nodeからts-node-esmに切り替えさせる方法もない。
ESMを使う際にはts-mochaを回避する方向性でいかなければならない。
Mocha -- ts-node Docsを参考にして、Mocha単体でコンパイルまでやってくれるように設定しておく。
現状の解決法1
ESMを避け、CommonJSだけで完結するようにする。
現状の解決法2
(1) 以下をtsconfigに付記する。
code:tsconfig.json
"module": "ES2015", //"ES~~"系であれば問題なし。
"moduleResolution": "nodenext",
この上で、importに使うファイルの拡張子に.jsを付記しておく。
(2) そして、以下の設定ファイルを用意した上でmochaを直接使ってテストする。
code:.mocharc.json
{
"require": "ts-node/register",
"loader": "ts-node/esm",
"extensions": "ts", "tsx"
}
これらのプロパティはts-nodeへ渡されることになる。
Mocha -- ts-node Docs参照。
ここに載ってる上側の方のコマンドラインの実行法ではESMに全く対応してくれない。
.mocharc.jsonを使った方法のみが唯一の解決策となる。
CLI上での--loaderオプションは、mocha側で対応していないため。
現地点(2023-04-04 03:57)で、mocha側のドキュメントにこれの記述がない。
.をファイル名の先頭に先頭につけていることに注意
loaderがないとTypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts"が発生する。
esm Loaders(experimental) -- Node.js Doc
To customize the default module resolution, ...
とあるので、このESMLoaderによってモジュール解決法を操作できるらしい。
Nodeの機能。
考慮点
experimental(実験的機能)に依存している点。
プロダクトに使える解決策ではない。
Native ECMAScript modules - ts-node
Node's ESM loader hooks are experimental and subject to change. ts-node's ESM support is as stable as possible, but it relies on APIs which node can and will break in new versions of node. Thus it is not recommended for production.
ts-node自体がESMに対応しきれていないのが現状。
(Node.jsすらもが最近技術的チャレンジを乗り越えようやくESMに対応したからね...)
と考えると、ts-nodeを使った開発でESMを使おうとするのがそもそも無理なのかもしれない。
現状は解決策1がもっとも望ましい。
感想
そもそもESMに固執しようとしなければこんなことには...
でも、今回の調べ学習のおかげで、公式ドキュメントやGitHub Issuesを巡って対処しようとする姿勢はもっと身についたと思う。
また、TypeScriptにもっと詳しくなれた。
TypeScriptのModule Resolution Strategy(モジュール解決アルゴリズム)
完全ではないが、ts-mochaの動作原理も知れた
ts-mochaのざっくりとしたしくみ
あと、モジュールのブラックボックスを切り開いて解析するという自分の人生初なことも何気にやった。
出来ないと証明できたのでめちゃくちゃスッキリした。よかった〜〜〜....。
https://typestrong.org/ts-node/docs/troubleshooting/#err_unknown_file_extension
https://mochajs.org/#configuring-mocha-nodejs
https://www.te-nu.com/entry/2018/04/21/195248
https://nodejs.org/dist/latest-v16.x/docs/api/esm.html
https://www.typescriptlang.org/docs/handbook/esm-node.html
hr.icon
02:20 -03:19
次の何の依存も持たないtsファイルをts-mochaに乗せて、mochaへの渡し方が変わるか確認する
code:test.ts
console.log("a");
ファイル拡張子によってCJSとESMを変更してみる
.cts, mts --> いずれも正しく実行される。
type: moduleのありなし
いずれも正しく実行うされる。
あれ〜〜〜となるとts-mochaはESMだからといってmochaに直渡ししているわけではなさそう。
ESMに依存するモジュール(CJS/ESM)をts-mochaに入力すると、何故かエラーが出てくる現象について色々実験した
諦めた...。
00:39 - 01:20
直接ts-mochaを覗くということをどうして思いつかなかったのか...!
ts-mochaのざっくりとしたしくみを書いた
何やってるか、大雑把な流れはわかった。
ts-node APIのregisterがよくわかんなかった